-
Notifications
You must be signed in to change notification settings - Fork 2.6k
Fix duplicate rehydrate during reasoning; centralize rehydrate and preserve cancel metadata #8171
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
…ze rehydrate in provider and preserve cancel metadata - Remove Task-side rehydrate on stream error/cancel; provider owns rehydrate - Set abandoned early on user cancel to prevent Task catch from rehydrating - Provider writes cancelReason to last api_req_started and appends assistant interruption - Trim orphan reasoning-only UI rows on resume - Add defensive guards to avoid rehydrate-after-rehydrate
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
Centralizes task rehydration logic to prevent duplicate rehydrations during reasoning interruptions and preserves cancellation metadata. The changes move rehydration control from the Task class to the ClineProvider to avoid race conditions.
- Centralizes rehydration logic in ClineProvider with defensive guards against duplicate operations
- Preserves cancel metadata in both UI and API message histories during user-initiated cancellations
- Removes trailing reasoning-only UI messages during task rehydration to maintain consistency
Reviewed Changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| ClineProvider.ts | Centralizes rehydration in onTaskAborted handler with defensive instance checks, adds comprehensive cancel bookkeeping |
| Task.ts | Adds abortReason tracking, removes local rehydration logic, cleans up trailing reasoning messages during rehydration |
Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.
src/core/webview/ClineProvider.ts
Outdated
| try { | ||
| // Only rehydrate on genuine streaming failures. | ||
| // User-initiated cancels are handled by cancelTask(). | ||
| if ((instance as any).abortReason === "streaming_failed") { |
Copilot
AI
Sep 18, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using type assertion (instance as any) bypasses type safety. Consider adding abortReason to the Task interface or creating a proper type guard to check for this property safely.
| const parentTask = task.parentTask | ||
|
|
||
| // Mark this as a user-initiated cancellation so provider-only rehydration can occur | ||
| task.abortReason = "user_cancelled" |
Copilot
AI
Sep 18, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Direct property assignment to abortReason suggests this property may not be part of the Task interface. Consider adding this property to the Task interface or using a setter method to ensure type safety.
src/core/webview/ClineProvider.ts
Outdated
| // Update ui_messages: add cancelReason to last api_req_started | ||
| const messagesJson = await fs.readFile(uiMessagesFilePath, "utf8").catch(() => undefined) | ||
| if (messagesJson) { | ||
| const uiMsgs = JSON.parse(messagesJson) as ClineMessage[] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wrap the JSON.parse call on uiMessagesFilePath content in a try/catch to handle potential parsing errors gracefully.
This comment was generated because it violated a code review rule: irule_PTI8rjtnhwrWq6jS.
src/core/webview/ClineProvider.ts
Outdated
|
|
||
| // Provider-side cancel bookkeeping to mirror abortStream effects for user_cancelled | ||
| try { | ||
| // Update ui_messages: add cancelReason to last api_req_started |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Refactor the cancelTask method to extract UI and API history updating logic into smaller helper functions for improved readability and maintainability.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you for this comprehensive fix to the duplicate rehydration issue! The centralization of rehydration logic in the Provider and the defensive guards against race conditions are solid architectural improvements.
Review Summary
I've reviewed the changes and found that the previously raised type safety concerns have been addressed - abortReason is now properly defined in the Task class.
Suggestions for Further Improvement
-
Type assertion could be avoided - In
ClineProvider.ts, the code uses(instance as any).abortReasoneven thoughabortReasonis properly defined in the Task class. Consider using proper typing instead. -
Potential infinite loop in reasoning cleanup - The while loop in
Task.tsthat removes trailing reasoning messages could theoretically run indefinitely if messages are malformed. Consider adding a safety limit. -
Duplicate cancel bookkeeping logic - The cancel bookkeeping logic appears in both
ClineProvider.cancelTask()andTask.abortStream(). Consider consolidating to reduce duplication. -
Extract magic strings as constants - Strings like
"streaming_failed","user_cancelled", and"[Response interrupted by user]"appear multiple times. Would be cleaner as named constants. -
Enhanced logging - Consider adding success logs when rehydration is performed, not just when it's skipped.
Overall, this is a well-thought-out solution that properly addresses the race condition issue. The changes effectively prevent duplicate rehydration and maintain proper state consistency.
- Mark original task instance abandoned instead of getCurrentTask() - Add parse guards and centralize UI/API cancel bookkeeping helpers - Replace any-cast with typed instance.abortReason - Centralize interruption strings and reuse in Task/Provider - Add final instanceId race-check before rehydrate - Remove unused variable in cancelTask()
|
Summary of applied fixes (items 1–6) Implemented
Additional cleanup
Notes
CI
|
d65a8f5 to
3bbf984
Compare
src/shared/messages.ts
Outdated
| * | ||
| * Note: These are plain phrases (no surrounding brackets). Call sites add any desired decoration. | ||
| */ | ||
| export const RESPONSE_INTERRUPTED_BY_USER = "Response interrupted by user" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These need to be internationalized
|
|
||
| try { | ||
| const existing = uiMsgs[idx]?.text ? JSON.parse(uiMsgs[idx].text as string) : {} | ||
| uiMsgs[idx].text = JSON.stringify({ ...existing, cancelReason: reason }) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we check if there's already a reason here before overwriting?
…lized strings and remove unused message constants
…iting - Add conditional check to preserve existing cancelReason values - Only set cancelReason if it doesn't already exist - Addresses review feedback from daniel-lxs
- Delete cancelBookkeeping.ts entirely - Remove bookkeeping imports and calls from ClineProvider - Simplify abortStream in Task.ts - Keep core fixes: centralized rehydration, abortReason tracking, abandoned flag - Let UI handle cancellation display without modifying persisted data
- Update log messages to reflect that we're no longer doing bookkeeping - Rename variable from currentAfterBookkeeping to currentAfterCheck - Make the code accurately describe what it's doing (race condition checks)
Closes #8153
Before:
FocuSee.Project.2025-09-19.18-07-19.mp4
AFTER
FocuSee.Project.2025-09-19.18-12-22.mp4
Problem
Task.recursivelyMakeClineRequests()
ClineProvider.cancelTask()
Changes
Centralize rehydration in the Provider
Task.recursivelyMakeClineRequests()
ClineProvider.onTaskAborted
Prevent double-create by marking “abandoned” earlier on user-cancel
ClineProvider.cancelTask()
Provider-side cancel bookkeeping to mirror abortStream for user cancels
ClineProvider.cancelTask()
ClineProvider.cancelTask()
Resume reconciliation for UI-only reasoning
Task.resumeTaskFromHistory()
Defensive safeguards to avoid rehydrate after rehydrate
ClineProvider.onTaskAborted
ClineProvider.cancelTask()
Why this approach
Behavioral impact
Key references
Task.recursivelyMakeClineRequests()
ClineProvider.onTaskAborted
ClineProvider.cancelTask()
Task.resumeTaskFromHistory()
Important
Centralizes task rehydration in
ClineProvider.tsto prevent duplicate rehydration during reasoning and updates localization for interruption messages.ClineProvider.ts, removing task-side rehydration inTask.ts.ClineProvider.tsnow handles rehydration only for streaming failures, not user cancels.apiConversationHistory.Task.ts: Removes task-side rehydration logic, addsabortReasonproperty.ClineProvider.ts: Implements centralized rehydration logic, updates cancel bookkeeping.This description was created by
for b1958e8. You can customize this summary. It will automatically update as commits are pushed.